/*
 * Copyright [2009] [Marcin Rzeźnicki]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */
package mr.go.yaxc;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Utility class for checking types
 * 
 * @author Marcin Rzeźnicki
 * 
 */
final class TypesHelper {

    @SuppressWarnings("unchecked")
    private static final Map<Class, Class> WRAPPERS =
            new HashMap<Class, Class>();

    static {
        WRAPPERS.put(byte.class,
                     Byte.class);
        WRAPPERS.put(short.class,
                     Short.class);
        WRAPPERS.put(char.class,
                     Character.class);
        WRAPPERS.put(int.class,
                     Integer.class);
        WRAPPERS.put(long.class,
                     Long.class);
        WRAPPERS.put(float.class,
                     Float.class);
        WRAPPERS.put(double.class,
                     Double.class);
        WRAPPERS.put(boolean.class,
                     Boolean.class);
    }

    /**
     * Checks whether {@code params} can be passed as actual arguments of method
     * which formal arguments are of {@code types}. This method takes into
     * account following pitfalls: boxing/unboxing, subtyping, null arguments
     *
     * @param types
     *            types of formal arguments
     * @param params
     *            actual arguments
     * @return true if {@code params} can be passed to method expecting
     *         arguments of {@code types}
     */
    public static boolean checkMatch(Class<?>[] types, Object[] params) {
        if (types.length != params.length) {
            return false;
        }
        int i = 0;
        for (Class<?> parameterType : types) {
            Object o = params[i++];
            if (o == null && !parameterType.isPrimitive()) {
                continue;
            }
            // workaround for isAssignableFrom bug
            if (parameterType.isPrimitive()) {
                parameterType = WRAPPERS.get(parameterType);
            }
            if (!parameterType.isAssignableFrom(o.getClass())) {
                return false;
            }
        }
        return true;
    }

    /**
     * Finds constructor of {@code clas} which can be invoked with {@code
     * paramValues}. This method takes into account following pitfalls:
     * boxing/unboxing, subtyping, null arguments
     *
     * @param <T>
     *            type of created object
     * @param clas
     *            Checked class
     * @param paramValues
     *            parameters of constructor
     * @return matching constructor or null if not found
     */
    public static <T> Constructor<T> findConstructor(Class<? extends T> clas,
                                                     Object[] paramValues) {
        Constructor<T> constructor = null;
        final Constructor<?>[] constructors = clas.getConstructors();
        for (Constructor<?> c : constructors) {
            if (checkMatch(c.getParameterTypes(),
                           paramValues)) {
                constructor = (Constructor<T>) c;
            }
        }
        return constructor;
    }

    /**
     * Finds version of method {@code methodName} of {@code clas} which can be
     * invoked with {@code paramValues}. This method takes into account
     * following pitfalls: boxing/unboxing, subtyping, null arguments
     *
     * @param clas
     *            Checked class
     * @param methodName
     *            name of method
     * @param paramValues
     *            parameters of method
     * @return matching method or null if not found
     */
    public static Method findMethod(Class<?> clas, String methodName,
                                    Object[] paramValues) {
        Method method = null;
        final Method[] methods = clas.getMethods();
        for (Method m : methods) {
            if (!m.getName().equals(methodName)) {
                continue;
            }
            if (checkMatch(m.getParameterTypes(),
                           paramValues)) {
                method = m;
            }
        }
        return method;
    }
}
